<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>JS的继承</title>
</head>

<body>
    <h1>JS的继承</h1>
    <ol>
        <li>
            <b>原型链继承</b>
            <p>创建一个子类将原型只想父类的实例对象，就能访问父类的属性和方法，这样就构成了原型链继承；</p>
            <p>优点：写法方便简洁，容易理解，代码复用；</p>
            <p>缺点：对象实力共享所有继承的属性和方法，修改了父类就会影响所有继承。无法向父类构造函数传参；</p>
        </li>
        <li>
            <b>借用构造函数继承</b>
            <p>在子类构造函数的内部调用父类型构造函数，使用 apply() 或 call() 方法将父对象的构造函数绑定在子对象上；</p>
            <p>优点：解决了原型链实现继承不能传参问题和父类的原型共享问题；</p>
            <p>缺点：借用构造函数的缺点是方法都在构造函数中定义，因此无法实现函数复用； <br /> 在父类的原型中定义的方法，对子类型而言也是不可见的，结果所有类型都只能使用费函数构造模式；</p>
        </li>
        <li>
            <b>组合式继承</b>
            <p>将 <i>原型链</i> 和 <i>组合式</i> 的继承方式组合在一起，使用原型链实现对原型属性和方法的继承；</p>
            <p>而通过借用构造函数来实现对实例属性的基础，这样几通过原型上定义方法实现函数复用，又能够保证每个实例都有自己的属性；</p>
            <p>优点：就是为了解决原型链继承和借用构造函数继承的影响；</p>
            <p>缺点：无论在什么情况下都会两用两次父类构造函数，也就是在内存堆里面存储了两次；</p>
        </li>
        <li>
            <b>ES6的 class 类继承</b>
            <p>Class 类通过 extends 关键字实现继承，其实质是先创造出父类的 this 对象，然后用子类的构造函数修改 this；</p>
            <p>子类的构造方法中必须使用 super()，且只有在调用了 super() 之后才能使用 this，<br />因为子类的 this 对象是继承父类的 this 对象，然后对其进行加工，<br />而
                super() 表示的是父类的构造函数，用来新建父类 this 对象；</p>
            <p>优点：语法简单易懂，操作更方便</p>
            <p>缺点：并不是所有浏览器都支持 class 关键字</p>
        </li>
    </ol>

    <script>
        // **** 原型链继承
        // 让一个构造函数的原型是另一个类型的实例，那么这个构造函数 New 出来的实例就具有该实例的属性。
        // function Parent() {
        //     this.isShow = true
        //     this.info = {
        //         name: 'lee',
        //         age: 18 
        //     }
        // };
        // Parent.prototype.getInfo = function () {
        //     console.log(this.isShow)
        //     console.log(this.info)
        // }
        // function Child() { }
        // Child.prototype = new Parent()  // 创建一个子类并且等于父类，这样就构成了原型链继承

        // let Child1 = new Child()
        // Child1.info.gender = '男'
        // Child1.getInfo()

        // let Child2 = new Child()
        // Child2.isShow = false  // 这里修改并不会影响到全局的 Parent 调用，因为他自己创建了一个 isShow 副本
        // Child2.getInfo()

        // let Child3 = new Parent()
        // Child3.getInfo()





        // **** 借用构造函数继承
        // 在子类构造函数的内部调用父类型构造函数，使用 apply() 或 call() 方法将父对象的构造函数绑定在子对象上
        // function Parent(gender) {
        //     this.info = {
        //         name: 'lee',
        //         age: 18,
        //         gender
        //     }

        //     // 方法都得在构造函数里面定义
        //     this.getInfo = function () {
        //         console.log(this.info)
        //     }
        // };

        // // 在 Child 子类使用了借用构造函数，这种绑定父类 Parent.prototype 的方式是不生效的，需要在父类构造函数上去定义，并不用继承父类原型上的属性和方法
        // // Parent.prototype.Print = function () {
        // //     console.log('hello world');
        // // }

        // function Child(gender) {
        //     Parent.call(this, gender)
        // }

        // // 而子类的子类如果没有使用借用构造函数的话，是可以通过 prototype 把方法挂在原型上
        // Child.prototype.print = function () {
        //     console.log('hello world');
        // }

        // let child1 = new Child('女')
        // child1.info.nickname = 'xxx'
        // console.log(child1.info);

        // let child2 = new Child('not');
        // console.log(child2.info);
        // child2.getInfo()
        // child2.print()

        // console.log('查看整个链条：', child2);




        // **** 组合式继承
        // function Person(gender) {
        //     console.log('Parent 执行次数');
        //     this.info = {
        //         name: 'lee',
        //         age: 18,
        //         gender
        //     }
        // }

        // Person.prototype.getInfo = function () {
        //     console.log(this, this.info.name, this.info.age);
        // }

        // function Child(gender) {
        //     Person.call(this, gender)
        // }

        // Child.prototype = new Person()

        // let child1 = new Child('男')
        // child1.info.nickname = 'xxx'
        // child1.getInfo()

        // let child2 = new Child('女')
        // console.log(child2.info);




        // **** es6 Class 类继承
        class Animal {
            constructor(kind) {
                this.kind = kind
            }

            getKind() {
                return this.kind
            }
        }

        // 子类继承 Animal 
        class Cat extends Animal {
            constructor(name) {
                // 子类的构造方法必须先调用 super 方法
                super('cat')
                this.name = name 
            }

            getCatInfo() {
                console.log(this.name + "：" + super.getKind());
            }
        }

        const cat1 = new Cat('tom')
        cat1.getCatInfo()
    </script>
</body>

</html>